﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Daybreak.Core.SceneGraph;

namespace Daybreak.Core
{
    public partial class Scene
    {
        public interface IAnimation
        {
            void Update(int key);
            void Update(int key1, int key2, float lerp);
            void UpdateByTime(Transform target, float time);
            void UpdateTarget(Transform target, int key);
            void UpdateTarget(Transform target, int key1, int key2, float lerp);
            int KeysCount { get; }
        }

        /// <summary>
        /// Categorizes the declaration of animation information. The animation hierarchy contains elements that 
        /// describe the animation’s key-frame data and sampler functions, ordered in such a way as to group 
        /// together animations that should be executed together. 
        /// </summary>
        /// <remarks>
        /// Animation describes the transformation of an object or value over time. A common u
        /// give the illusion of motion. A common animation technique is key-frame animation. 
        /// </remarks>
        public class Animation : IAnimation
        {
            /// <summary>
            /// Animator method handler
            /// </summary>
            /// <param name="node">node to animate</param>
            /// <param name="lerp">time (from 0 to 1)</param>
            public delegate void Method(Transform node, float lerp);

            public string Name;

            /// <summary>
            /// Animated node
            /// </summary>
            public Transform TargetNode;

            private Matrix[] _matrixKeys;
            private float[] _timeKeys;

            public Animation(string name)
            {
                Name = name;
            }

            public Animation(string name, Matrix[] keys, float[] times)
            {
                Name = name;
                _matrixKeys = keys;
                _timeKeys = times;
            }

            public void UpdateByTime(Transform target, float time)
            {
                if (target == null)
                    target = TargetNode;

                float[] keys = TimeKeys;

                for (int i = 0; i < keys.Length - 1; i++)
                {
                    if (time >= keys[i] && time < keys[i + 1])
                    {
                        float sampleLen = keys[i + 1] - keys[i];
                        time -= keys[i];
                        float lerp = time / sampleLen;

                        UpdateTarget(target, i, i + 1, lerp);
                    }
                }

                if (time >= keys[keys.Length - 1])
                    UpdateTarget(target, keys.Length - 1);
            }

            public void Update(int key1, int key2, float lerp)
            {
                if (TargetNode == null)
                    return;

                Matrix result;

                Matrix.Lerp(ref MatrixKeys[key1], ref MatrixKeys[key2], lerp, out result);

                TargetNode.SetTransformation(ref result);
            }

            public void Update(int key)
            {
                if (TargetNode == null)
                    return;

                TargetNode.SetTransformation(ref MatrixKeys[key]);
            }

            public void UpdateTarget(Transform target, int key1, int key2, float lerp)
            {
                if (target == null)
                    return;

                Matrix result;

                Matrix.Lerp(ref MatrixKeys[key1], ref MatrixKeys[key2], lerp, out result);

                target.SetTransformation(ref result);
            }

            public void UpdateTarget(Transform target, int key)
            {
                if (target == null)
                    return;

                target.SetTransformation(ref MatrixKeys[key]);
            }

            public virtual Matrix[] MatrixKeys
            {
                get { return _matrixKeys; }
            }

            public virtual float[] TimeKeys
            {
                get { return _timeKeys; }
            }

            /// <summary>
            /// Sampler interpolation type 
            /// </summary>
            public virtual string Interpolation
            {
                get { return "LINEAR"; }
            }

            public int KeysCount
            {
                get { return MatrixKeys.Length; }
            }

            public override string ToString()
            {
                return string.Format("<animation id='{0}'> node:{1}", Name, TargetNode.Name);
            }

        }

        /// <summary>
        /// Defines a section of the animation curves to be used together as an animation clip. 
        /// </summary>
        public class AnimationClip
        {
            public string Name;

            /// <summary>
            /// The time in seconds of the beginning of the clip.
            /// </summary>
            public float Start;
            /// <summary>
            /// The time in seconds of the end of the clip.
            /// </summary>
            public float End;

            public List<IAnimation> Animations = new List<IAnimation>();

            public AnimationClip(string name)
            {
                Name = name;
            }

            public override string ToString()
            {
                return string.Format("<animation_clip id='{0}' start={1} end={2}> ", Name, Start, End);
            }

        }

        public class AnimationInstance
        {
            private List<IAnimation> _clipTracks;
            private float _duration;
            protected float _clipTime;
            protected int _counter;
            public bool Loop;
            public bool Running = true;

            public AnimationInstance()
            {
            }

            protected virtual void Start() { }

            public AnimationInstance(List<IAnimation> clip, float duration)
            {
                _duration = duration;
                _clipTracks = clip;
            }

            public virtual void Advance(float delta)
            {
                if (!Running)
                    return;

                _clipTime += delta;

                if (_clipTime >= _duration)
                {
                    if (!Loop)
                        Running = false;

                    _clipTime = _clipTime - _duration;

                    for (int i = 0; i < _clipTracks.Count; i++)
                    {
                        if (_clipTracks[i] == null)
                            continue;

                        int key = _clipTracks[i].KeysCount - 1;
                        _clipTracks[i].Update(key);
                    }
                }
                else
                {
                    for (int i = 0; i < _clipTracks.Count; i++)
                    {
                        if (_clipTracks[i] == null)
                            continue;

                        _clipTracks[i].UpdateByTime(null, _clipTime);
                    }

                }
            }
        }

        public class AnimationChain : AnimationInstance
        {
            AnimationInstance[] _movies;
            int _current;

            public AnimationChain(AnimationInstance[] clips)
            {
                _movies = clips;
            }

            public override void Advance(float delta)
            {
                if (!Running)
                    return;

                if (!_movies[_current].Running)
                    _current++;

                if (_current >= _movies.Length)
                {
                    Running = false;
                    return;
                }

                _movies[_current].Advance(delta);
            }
        }

        /// <summary>
        /// Declares a module of Animation and AnimationClip objects
        /// </summary>
        public class AnimationLib
        {
            public List<Animation> AnimationList = new List<Animation>();
            public List<AnimationClip> AnimationClipList = new List<AnimationClip>();

            public AnimationLib()
            { }

            public AnimationClip FindClip(string name)
            {
                for (int i = 0; i < AnimationClipList.Count; i++)
                    if (AnimationClipList[i].Name == name)
                        return AnimationClipList[i];

                return null;

            }

            public Animation FindByName(string name)
            {
                for (int i = 0; i < AnimationList.Count; i++)
                    if (AnimationList[i].Name == name)
                        return AnimationList[i];

                return null;

            }

            public Animation FindByNode(Transform node)
            {
                for (int i = 0; i < AnimationList.Count; i++)
                    if (AnimationList[i].TargetNode == node)
                        return AnimationList[i];

                return null;

            }

            public Animation AddBackedAnimation(string name, Transform node, Matrix[] keys, float[] times)
            {
                Animation anim = new Animation(name, keys, times);
                anim.TargetNode = node;

                AnimationList.Add(anim);

                return anim;
            }

            public AnimationClip AddClip(string name)
            {
                Scene.AnimationClip clip = new Scene.AnimationClip(name);
                AnimationClipList.Add(clip);

                return clip;

            }

        }


    }
}
